今天我們進入BDD實作的章節,首先我們把昨天準備好的Gherkin feature描述檔拿來應用,這個檔案可以不特別指定路徑但我建議放在我們這個系列一開始介紹過的unit test指定的test folder裡方便管理,任一文字編輯軟體都可以寫feature檔,記得副檔名存成.feature就可以,這個範例我建立一個叫feature的資料夾來放置下面的範例feature檔案test.feature。
File path => src/test/feature/test.feature
Feature: Main page ui
Scenario: loading user in main page
Given username from server
When request username in main page
Then received username in main page
當放置好範例feature檔後,我們下一步要建立一個可以透過Cucumber來執行的runner檔案,這個檔案放置在原本單元測試的目錄下就可以了,class取名TestRunner,這個class不用實作任何細節,只是透過Annotation的方式來設定Cucumber runner。
File path => src/test/java/<package name>/TestRunner.class
而這裡必須指定的有@RunWith這一行是指透過cucumber runner來呼叫test case,@CucumberOptions裡有許多參數可以指定,我們這裡只需要指定要讀取的feature檔案路徑。如果今天的feature不只一個檔案的話就利用array的型式指定給cucumber runner來讀取。
@RunWith(Cucumber::class)
@CucumberOptions(features = arrayOf("src/test/feature/test.feature"))
class TestRunner
設置完TestRunner後我們先試著執行一次試試看,結果就出現了以下錯誤訊息,cucumber runner通知我們沒有實作在test.feature檔裡定義的Given/When/Then描述,並且很貼心的建議我們要實作下圖紅框裡的程式碼,也就是它自動產生code snippet讓我們貼到test file裡。
我們把cucumber runner建議的code snippet copy下來paste在另一個檔案DemoTest,這裡必須注意的是cucumber runner還不會幫我們產生Kotlin format的code snippet,你看到的是Java format的code snippet,不過還好Android Studio會幫我們自動修正成Kotlin format。在DemoTest中我們必須實作En這個Cucumber的interface這樣可以讓Cucumber可以直接讀取語意化的function而不用透過Annotation的方式。
當然這時候function內的實作都是空的,我們必須把PendingException這些block移除掉後就可以把我們單元測試的code依定義放進去。
class DemoTest : En {
init {
Given("username from server") {
// Write code here that turns the phrase above into concrete actions
throw cucumber.api.PendingException()
}
When("request username in main page") {
// Write code here that turns the phrase above into concrete actions
throw cucumber.api.PendingException()
}
Then("received username in main page") {
// Write code here that turns the phrase above into concrete actions
throw cucumber.api.PendingException()
}
}
}
我們這時候把在MVP章節的範例放進來看看,首先宣告需要當全局變數的mock物件,然後在Given區塊內把我們預設的every回傳行為寫在這裡。在When區塊內執行presenter的被測function,最後在Then的區塊內執行驗證行為,也就是verify或是assertEquals等驗證方式。
class DemoTest : En {
init {
val view = mockk<IView>()
val server = mockk<Server>()
Given("username from server") {
every {
view.receivedUserName(any())
} just Runs
every {
server.requestUserName()
}.returns(User("Daniel", "Chen"))
}
When("request username in main page") {
var presenter = MainActivityPresenter(view, server)
presenter.requestUserName()
}
Then("received username in main page") {
verify {
view.receivedUserName(any())
}
}
}
}
寫好test後我們再去執行剛剛建立的TestRunner,TestRunner就會依照test.feature裡面所定義的語意function自動在test folder下搜尋這些testing function,然後依Given/When/Then的方式執行並且在Debug的視窗中report結果如下圖。
我們把Cucumber實作BDD的流程很快速的介紹完了,我們是已經把前面寫好的Test拿來用,感覺變成是只有copy & paste,其實應該要先從BDD的planning階段開始再去寫做前面章節的Unit test,看完這篇再回去看前面可能會有不一樣的感覺。再者即便你不使用Cucumber一樣能做BDD,你也可以使用Excel來寫這些given/when/then的條件但就是管理上會很麻煩,這是門只重其意不重其型的學問,不斷的深思熟慮一定會有收獲。